/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools resize defrag
 */

#include "resize_defrag.h"

#include <string>
#include "securec.h"
#include "resize_operator.h"
#include "hmfs_common.h"
#include "hmfs_zoned.h"

namespace OHOS {
namespace Hmfs {
ResizeDefrag::ResizeDefrag(ResizeOperator *optPtr, std::shared_ptr<HmfsSuperBlockInfo> &superBlockInfo,
    std::shared_ptr<HmfsConfigData> &configData)
    : opt_(optPtr), superBlockInfo_(superBlockInfo), config_(configData) {}

unsigned short ResizeDefrag::GetSegVblocks(SegmentEntry *sitEntry)
{
    if (!NeedFsyncDataRecord(superBlockInfo_, config_)) {
        return sitEntry->validBlocks;
    } else {
        return sitEntry->checkPointValidBlocks;
    }
}

unsigned char *ResizeDefrag::GetSegBitmap(SegmentEntry *sitEntry)
{
    if (!NeedFsyncDataRecord(superBlockInfo_, config_)) {
        return sitEntry->currentValidBitmap;
    } else {
        return sitEntry->checkpointValidBitmap;
    }
}

unsigned char ResizeDefrag::GetSegType(SegmentEntry *sitEntry)
{
    if (!NeedFsyncDataRecord(superBlockInfo_, config_)) {
        return sitEntry->type;
    } else {
        return sitEntry->checkPointType;
    }
}

void ResizeDefrag::SetSectionType(uint32_t sitNumber, int type)
{
    if (superBlockInfo_->segmentsPerSection == 1) {
        return;
    }
    for (uint32_t i = 0; i < superBlockInfo_->segmentsPerSection; i++) {
        SegmentEntry *sitEntry = GetSegEntry(superBlockInfo_, sitNumber + i);
        sitEntry->type = type;
    }
}

bool ResizeDefrag::WritePointerAtZoneStart(uint32_t zoneSegmentNumber)
{
#ifdef HAVE_LINUX_BLKZONED_H
    uint32_t block = START_BLOCK(superBlockInfo_, zoneSegmentNumber);
    int logSectorsPerBlock = superBlockInfo_->logBlockSize - SECTOR_SIZE_SHIFT;

    if (config_->zonedModel != HMFS_ZONED_HM) {
        return true;
    }
    uint32_t j = 0;
    for (j = 0; j < MAX_DEVICE_COUNT; j++) {
        if (!config_->devices[j].path.c_str()) {
            break;
        }
        if (config_->devices[j].startBlockId <= block &&
            block <= config_->devices[j].endBlockId) {
            break;
        }
    }

    if (j >= MAX_DEVICE_COUNT) {
        return false;
    }
    uint64_t sector = (block - config_->devices[j].startBlockId) << logSectorsPerBlock;
    int32_t fd = config_->devices[j].fd;
    BlockZone blockZone;
    int32_t ret = HmfsZoned::GetInstance().HmfsReportZone(fd, sector, &blockZone);
    if (ret) {
        return false;
    }
    if (GetBlockZoneType(&blockZone) != BLK_ZONE_TYPE_SEQWRITE_REQ) {
        return true;
    }

    return GetBlockZoneSector(&blockZone) == GetBlockZoneWpSector(&blockZone);
#else
    return true;
#endif
}

uint64_t ResizeDefrag::CalculateEndBlockAddress()
{
    SuperBlockData *superBlockData = superBlockInfo_->rawSuper;
    return (GetLeValue(superBlockData->segmentCountInMain) <<
        GetLeValue(superBlockData->logBlksPerSeg)) + GetLeValue(superBlockData->mainBlockId);
}

int32_t ResizeDefrag::CheckFreeSegments()
{
    if (GetFreeSegments(superBlockInfo_, config_) <= superBlockInfo_->segmentTable->reservedSegments + 1) {
        return 1;
    }
    return 0;
}

void ResizeDefrag::ProcessNextBlock(uint64_t *to, int left, uint32_t sitNumber)
{
    if (*to >= superBlockInfo_->segmentTable->mainBlockAddr && *to < CalculateEndBlockAddress()) {
        *to = left ? *to - 1 : *to + 1;
    }
}

int32_t ResizeDefrag::CheckSegmentFull(SegmentEntry *sitEntry)
{
    unsigned short vblocks = GetSegVblocks(sitEntry);
    return (vblocks == superBlockInfo_->blocksPerSeg);
}

int32_t ResizeDefrag::ProcessEmptySegment(uint32_t sitNumber, int32_t wantType)
{
    SegmentEntry *se2;
    uint32_t i;
    for (i = 1; i < superBlockInfo_->segmentsPerSection; i++) {
        se2 = GetSegEntry(superBlockInfo_, sitNumber + i);
        if (GetSegVblocks(se2)) {
            return EXIT_ERR_CODE;
        }
    }

    if (i == superBlockInfo_->segmentsPerSection && WritePointerAtZoneStart(sitNumber)) {
        HMFS_DEBUG("SetSectionType");
        SetSectionType(sitNumber, wantType);
        return SUCCESS_CODE;
    }
    return EXIT_ERR_CODE;
}

bool ResizeDefrag::CheckValidBlock(uint32_t offset, unsigned char *bitmap, unsigned char type,
    int wantType, bool newSec)
{
    if (type == wantType && !newSec &&
        !HmfsCommon::GetInstance().VerifyBit(offset, bitmap)) {
        HMFS_DEBUG("type == wantType");
        return true;
    }
    return false;
}

int32_t ResizeDefrag::FindNextFreeBlock(uint64_t *to, int left, int wantType, bool newSec)
{
    SuperBlockData *superBlockData = superBlockInfo_->rawSuper;
    int notEnough = 0;
    uint64_t endBlockAddr = CalculateEndBlockAddress();
    if (*to > 0) {
        *to -= left;
    }
    if (CheckFreeSegments()) {
        notEnough = 1;
    }
    while (*to >= superBlockInfo_->segmentTable->mainBlockAddr && *to < endBlockAddr) {
        uint32_t sitNumber = GetSegmentNumber(superBlockInfo_, *to);
        uint32_t offset = OffsetInSegment(superBlockInfo_, *to);
        SegmentEntry *sitEntry = GetSegEntry(superBlockInfo_, sitNumber);
        unsigned short vblocks = GetSegVblocks(sitEntry);
        unsigned char *bitmap = GetSegBitmap(sitEntry);
        unsigned char type = GetSegType(sitEntry);
        if (CheckSegmentFull(sitEntry)) {
            *to = left ? START_BLOCK(superBlockInfo_, sitNumber) - 1 : START_BLOCK(superBlockInfo_, sitNumber + 1);
            continue;
        }
        if (!(GetLeValue(superBlockData->features) & NativeToLE32(HMFS_FEATURE_RO)) &&
            IsCurrentSegmentNumber(superBlockInfo_, sitNumber)) {
            *to = left ? START_BLOCK(superBlockInfo_, sitNumber) - 1 : START_BLOCK(superBlockInfo_, sitNumber + 1);
            continue;
        }
        if (vblocks == 0 && notEnough) {
            *to = left ? START_BLOCK(superBlockInfo_, sitNumber) - 1 : START_BLOCK(superBlockInfo_, sitNumber + 1);
            continue;
        }
        if (vblocks == 0 && !(sitNumber % superBlockInfo_->segmentsPerSection)) {
            if (ProcessEmptySegment(sitNumber, wantType) == 0) {
                return SUCCESS_CODE;
            }
        }
        if (CheckValidBlock(offset, bitmap, type, wantType, newSec)) {
            return SUCCESS_CODE;
        }
        *to = left ? *to - 1 : *to + 1;
    }
    return EXIT_ERR_CODE;
}

void ResizeDefrag::MoveOneCursegInfo(uint64_t from, int left, int i)
{
    SuperBlockData *superBlockData = superBlockInfo_->rawSuper;
    CurrentSegmentInfo *currentSegment = GetCurrentSegment(superBlockInfo_, i);
    int32_t ret = 0;
    uint64_t ssaBlock = 0;
    if ((GetLeValue(superBlockData->features) & NativeToLE32(HMFS_FEATURE_RO))) {
        if (i != CURSEG_DATA_HOT && i != CURSEG_NODE_HOT) {
            return;
        }
        if (i == CURSEG_DATA_HOT) {
            left = 0;
            from = superBlockInfo_->segmentTable->mainBlockAddr;
        } else {
            left = 1;
            from = EndBlockAddr(superBlockInfo_);
        }
    } else {
        ssaBlock = GetSumBlkaddr(superBlockInfo_, currentSegment->segmentNumber);
        ret = HmfsIo::GetInstance().DevWriteBlock(currentSegment->summaryBlockData, ssaBlock);
        HMFS_ASSERT(ret >= 0);
    }
    uint64_t to = from;
    ret = FindNextFreeBlock(&to, left, i, config_->zonedModel == HMFS_ZONED_HM);
    HMFS_ASSERT(ret == 0);

    currentSegment->segmentNumber = GetSegmentNumber(superBlockInfo_, to);
    currentSegment->nextBlockOffset = OffsetInSegment(superBlockInfo_, to);
    currentSegment->allocType = config_->zonedModel == HMFS_ZONED_HM ? static_cast<int32_t>(AllocType::LFS) :
        static_cast<int32_t>(AllocType::SSR);

    ssaBlock = GetSumBlkaddr(superBlockInfo_, currentSegment->segmentNumber);
    SummaryBlockData buf = {};
    ret = HmfsIo::GetInstance().DevReadBlock(&buf, ssaBlock);
    HMFS_ASSERT(ret >= 0);

    ret = memcpy_s(currentSegment->summaryBlockData, sizeof(buf.entries), buf.entries, sizeof(buf.entries));
    HMFS_CHECK_ONLY_LOG(ret == 0, "Failed to copy summaryBlockData");

    ResetCurSegment(superBlockInfo_, i);
}

void ResizeDefrag::MoveCursegInfo(uint64_t from, int left)
{
    for (int i = 0; i < CURSEG_TYPE_MAX; i++) {
        MoveOneCursegInfo(from, left, i);
    }
}

void ResizeDefrag::ZeroJournalEntries()
{
    for (int i = 0; i < CURSEG_TYPE_MAX; i++) {
        GetCurrentSegment(superBlockInfo_, i)->summaryBlockData->journal.natCount = 0;
    }
}

void ResizeDefrag::WriteCursegInfo()
{
    CheckPointData *checkPointData = superBlockInfo_->checkPointData;
    for (int32_t i = 0; i < CURSEG_TYPE_MAX; i++) {
        checkPointData->allocType[i] = GetCurrentSegment(superBlockInfo_, i)->allocType;
        if (i < CURSEG_NODE_HOT) {
            SetLeValue(checkPointData->curDataSegNo[i], GetCurrentSegment(superBlockInfo_, i)->segmentNumber);
            SetLeValue(checkPointData->curDataBlkOffset[i], GetCurrentSegment(superBlockInfo_, i)->nextBlockOffset);
        } else {
            int32_t n = i - CURSEG_NODE_HOT;
            SetLeValue(checkPointData->curNodeSegNo[n], GetCurrentSegment(superBlockInfo_, i)->segmentNumber);
            SetLeValue(checkPointData->curNodeBlkOffset[n], GetCurrentSegment(superBlockInfo_, i)->nextBlockOffset);
        }
    }
}

void ResizeDefrag::RewriteCurrentSitPage(uint32_t sitNumber, SitBlockData *sitBlk)
{
    uint32_t blkAddr = CurrentSitAddr(superBlockInfo_, sitNumber);
    HMFS_ASSERT(HmfsIo::GetInstance().DevWriteBlock(sitBlk, blkAddr) >= 0);
}

void ResizeDefrag::FlushSitEntries()
{
    SitInfo *sitInfo = superBlockInfo_->segmentTable->sitInfo;
    SitBlockData *sitBlk = static_cast<SitBlockData *>(calloc(BLOCK_SZ, 1));
    HMFS_ASSERT(sitBlk);
    for (uint32_t sitNumber = 0; sitNumber < MAIN_SEGS(superBlockInfo_); sitNumber++) {
        SegmentEntry *sitEntry = GetSegEntry(superBlockInfo_, sitNumber);
        if (!sitEntry->dirty) {
            continue;
        }
        GetCurrentSitPage(superBlockInfo_, sitNumber, sitBlk);
        SitEntry *sit = &sitBlk->entries[SIT_ENTRY_OFFSET(sitInfo, sitNumber)];
        int32_t ret = memcpy_s(sit->blockBitmap, SIT_VBLOCK_MAP_SIZE, sitEntry->currentValidBitmap,
            SIT_VBLOCK_MAP_SIZE);
        HMFS_CHECK_ONLY_LOG(ret == 0, "Failed to copy blockBitmap");
        sit->usedBlocks = NativeToLE16((sitEntry->type << SIT_USER_BLOCK_COUNT_SHIFT) | sitEntry->validBlocks);
        RewriteCurrentSitPage(sitNumber, sitBlk);
    }
    free(sitBlk);
}

void ResizeDefrag::UpdateSumEntry(uint32_t blockAddr, SummaryEntry *sum)
{
    SuperBlockData *superBlockData = superBlockInfo_->rawSuper;
    if (GetLeValue(superBlockData->features) & NativeToLE32(HMFS_FEATURE_RO)) {
        return;
    }
    uint32_t sitNumber = GetSegmentNumber(superBlockInfo_, blockAddr);
    uint32_t offset = OffsetInSegment(superBlockInfo_, blockAddr);
    SitInfo *sitInfo = superBlockInfo_->segmentTable->sitInfo;
    SegmentEntry *sitEntry = &sitInfo->segEntries[sitNumber];
    int32_t type = 0;
    SummaryBlockData *summaryBlock = opt_->GetSumBlock(sitNumber, &type);
    int retCode = memcpy_s(&summaryBlock->entries[offset], sizeof(*sum), sum, sizeof(*sum));
    HMFS_CHECK_ONLY_LOG(retCode == 0, "Failed to copy entries of SummaryBlockData");
    summaryBlock->footer.entryType = IsNodeSegement(sitEntry->type) ? SUM_TYPE_NODE : SUM_TYPE_DATA;
    uint32_t summaryBlockAddr = (superBlockInfo_->segmentTable->ssaBlockAddr) + sitNumber;
    int ret = HmfsIo::GetInstance().DevWriteBlock(summaryBlock, summaryBlockAddr);
    HMFS_ASSERT(ret >= 0);
    if (type == SEG_TYPE_NODE || type == SEG_TYPE_DATA || type == SEG_TYPE_MAX) {
        free(summaryBlock);
    }
}

int32_t ResizeDefrag::HmfsMigrateBlock(uint64_t from, uint64_t to)
{
    void *raw = calloc(BLOCK_SZ, 1);
    HMFS_ASSERT(raw);
    int ret = HmfsIo::GetInstance().DevReadBlock(raw, from);
    HMFS_ASSERT_FREE(ret >= 0, raw);
    ret = HmfsIo::GetInstance().DevWriteBlock(raw, to);
    free(raw);
    HMFS_ASSERT(ret >= 0);
    SitInfo *sitInfo = superBlockInfo_->segmentTable->sitInfo;
    SegmentEntry *sitEntry = &sitInfo->segEntries[GetSegmentNumber(superBlockInfo_, from)];
    uint64_t offset = OffsetInSegment(superBlockInfo_, from);
    int type = sitEntry->type;
    sitEntry->validBlocks--;
    HmfsCommon::GetInstance().ClearBit(offset, sitEntry->currentValidBitmap);
    sitEntry->dirty = 1;

    sitEntry = &sitInfo->segEntries[GetSegmentNumber(superBlockInfo_, to)];
    offset = OffsetInSegment(superBlockInfo_, to);
    sitEntry->type = type;
    sitEntry->validBlocks++;
    HmfsCommon::GetInstance().SetBit(offset, sitEntry->currentValidBitmap);
    sitEntry->dirty = 1;

    SummaryEntry sum = {};
    opt_->GetSumEntry(from, &sum);
    UpdateSumEntry(to, &sum);
    if (IsDataSegement(type)) {
        opt_->UpdateDataBlkaddr(LE32ToNative(sum.nodeId), LE16ToNative(sum.nodeBlockOffset), to);
    } else {
        opt_->UpdateNatBlkaddr(0, LE32ToNative(sum.nodeId), to);
    }
    return 0;
}

int32_t ResizeDefrag::HmfsDefragment(uint64_t from, uint64_t len, uint64_t to, int left)
{
    opt_->FlushJournalEntries();
    for (uint64_t idx = from; idx < from + len; idx++) {
        uint64_t target = to;
        SitInfo *sitInfo = superBlockInfo_->segmentTable->sitInfo;
        SegmentEntry *sitEntry = &sitInfo->segEntries[GetSegmentNumber(superBlockInfo_, idx)];
        uint64_t offset = OffsetInSegment(superBlockInfo_, idx);
        if (!HmfsCommon::GetInstance().VerifyBit(offset, sitEntry->currentValidBitmap)) {
            continue;
        }
        if (FindNextFreeBlock(&target, left, sitEntry->type, false)) {
            HMFS_ERROR("Not enough space to migrate blocks");
            return -1;
        }
        if (HmfsMigrateBlock(idx, target)) {
            HMFS_ERROR("Found inconsistency: please run FSCK");
            return -1;
        }
    }
    MoveCursegInfo(to, left);
    ZeroJournalEntries();
    WriteCursegInfo();
    FlushSitEntries();
    opt_->WriteCheckpoint();
    return 0;
}
} // namespace Hmfs
} // namespace OHOS